//
// Created by guoshunw on 24-3-29.
//

#include "app_context.h"
#include "enum_to_string.h"

#include <OMX_Audio.h>
#include <OMX_Component.h>

extern "C" {
#include <omx_base_source.h>
#include <tsemaphore.h>
}

#include <cstring>
#include <fstream>

namespace openmax::test {

#if 0
    /** Specification version*/
#define VERSIONMAJOR 1
#define VERSIONMINOR 1
#define VERSIONREVISION 0
#define VERSIONSTEP 0

    static void setHeader(OMX_PTR header, OMX_U32 size) {
        auto ver = (OMX_VERSIONTYPE *) (static_cast<OMX_U8 *>(header) + sizeof(OMX_U32));
        *((OMX_U32 *) header) = size;

        ver->s.nVersionMajor = VERSIONMAJOR;
        ver->s.nVersionMinor = VERSIONMINOR;
        ver->s.nRevision = VERSIONREVISION;
        ver->s.nStep = VERSIONSTEP;
    }
#endif

OMX_ERRORTYPE AppContext::Init() {
  std::string info = "Init the Omx core";
  auto err = OMX_Init();
  LOG_RETURN_VAL_IF(err != OMX_ErrorNone, err)
  LOGI << info + " succeeded.";

  auto comp_name = const_cast<const OMX_STRING>(AUDIO_SINK);
  sink_event_sem_ = std::make_shared<tsem_t>();
  tsem_init(sink_event_sem_.get(), 0);
  audio_sink_callbacks_ = std::make_shared<openmax::callback::OMXCallbackBase>(
      this, const_cast<const OMX_STRING>(AUDIO_SINK), sink_event_sem_.get());
  err = OMX_GetHandle(&audio_sink_handle_, comp_name, audio_sink_callbacks_.get(), audio_sink_callbacks_.get());

  LOG_RETURN_VAL_IF(err != OMX_ErrorNone, err)
  LOGI << "The " << comp_name << " handle is: " << audio_sink_handle_;
//  input_pcm_file_ = "/home/guoshunw/Downloads/libomxil-bellagio/build/test/components/audio/output.raw";
  if (!input_pcm_file_.empty()) {
    auto ptr = new std::ifstream(input_pcm_file_, std::ios::binary | std::ios::in);
    input_pcm_file_stream_ = std::shared_ptr<std::ifstream>(ptr, [this](std::ifstream *ptr) {
      if (nullptr != ptr) {
        LOGI << "Close input pcm file: " << input_pcm_file_;
        ptr->close();
      }
    });
    if (!ptr->is_open()) {
      LOGE << "Open input pcm file " << input_pcm_file_ << " failed: " << std::strerror(errno);
    } else {
      LOGI << "Open input pcm file: " << input_pcm_file_ << " done.";
    }
  }
  return OMX_ErrorNone;
}

/**
 * DeInit the Omx core
 * */
OMX_ERRORTYPE AppContext::DeInit() {

  if (nullptr != audio_src_handle_) {
    auto err = OMX_FreeHandle(audio_src_handle_);
    if (err != OMX_ErrorNone) {
      LOGE << "Free audio source handle failed: " << GetEnumName(err);
    } else {
      LOGI << "Free audio source handle " << std::showbase << std::hex << audio_src_handle_ << " done.";
      audio_src_handle_ = nullptr;
    }
  }

  if (audio_sink_handle_) {
    auto err = OMX_FreeHandle(audio_sink_handle_);
    if (err != OMX_ErrorNone) {
      LOGE << "Free audio sink handle " << GetEnumName(err);
    } else {
      LOGI << "Free audio sink handle " << std::showbase << std::hex << audio_sink_handle_ << " done.";
      audio_sink_handle_ = nullptr;
    }
  }

  if (volume_handle_) {
    auto err = OMX_FreeHandle(volume_handle_);
    if (err != OMX_ErrorNone) {
      LOGE << "Free audio volume handle " << GetEnumName(err);
    } else {
      LOGI << "Free audio volume handle " << std::showbase << std::hex << volume_handle_ << " done.";
      volume_handle_ = nullptr;
    }
  }
  LOGI << "OMX de-initialized...";
  auto err = OMX_Deinit();
  if (err != OMX_ErrorNone) {
    return err;
  }

  return OMX_ErrorNone;
}
AppContext::AppContext() {
  LOGI << "Create app private type instance: " << std::showbase << std::hex << this;
  auto err = Init();
  if (err != OMX_ErrorNone) {
    LOGE << "Init failed: " << GetEnumName(err, true);
  }
}
AppContext::~AppContext() {
  auto err = DeInit();
  if (err != OMX_ErrorNone) {
    LOGE << "DeInit failed: " << GetEnumName(err, true);
  }

  LOGI << "Destroy app private type instance: " << std::showbase << std::hex << this;
}
OMX_ERRORTYPE AppContext::InitGain() {
  if (gain < 0 || gain > 100) {
    return OMX_ErrorBadParameter;
  }

  OMX_AUDIO_CONFIG_VOLUMETYPE sVolume{};

  auto err = OMX_GetConfig(volume_handle_, OMX_IndexConfigAudioVolume, &sVolume);
  if (err != OMX_ErrorNone) {
    LOGE << "Get config failed: " << std::showbase << std::hex << err;
    return err;
  }
  LOGI << "Current gain is: " << sVolume.sVolume.nValue << ", set to " << gain;
  sVolume.sVolume.nValue = gain;
  err = OMX_SetConfig(volume_handle_, OMX_IndexConfigAudioVolume, &sVolume);
  if (err != OMX_ErrorNone) {
    LOGE << "Set config failed: " << std::showbase << std::hex << err;
    return err;
  }
  return OMX_ErrorNone;
}
OMX_ERRORTYPE AppContext::Start() {
  OMX_ERRORTYPE err = OMX_ErrorInvalidComponent;
  LOG_RETURN_VAL_IF_NULL(audio_sink_handle_, err)

  // config audio src component
  ::setHeader(&s_pcm_mode_param_, sizeof(OMX_AUDIO_PARAM_PCMMODETYPE));
  err = OMX_GetParameter(audio_sink_handle_, OMX_IndexParamAudioPcm, &s_pcm_mode_param_);
  if (err != OMX_ErrorNone) {
    LOGE << "Get config failed: " << GetEnumName(err);
    return err;
  }
  s_pcm_mode_param_.nChannels = 2;
  //        s_pcm_mode_param_.nSamplingRate = 48000;
  s_pcm_mode_param_.nBitPerSample = 32;
  s_pcm_mode_param_.nSamplingRate = 44100;
  //        s_pcm_mode_param_.nBitPerSample = 32;

  err = OMX_SetParameter(audio_sink_handle_, OMX_IndexParamAudioPcm, &s_pcm_mode_param_);

  if (err != OMX_ErrorNone) {
    LOGE << "Set config failed: " << GetEnumName(err);
    return err;
  }
  LOGI << "Set pcm mode: " << s_pcm_mode_param_.nChannels << " channels, " << s_pcm_mode_param_.nSamplingRate
       << " sampling rate, " << s_pcm_mode_param_.nBitPerSample << " bit per sample";

  err = OMX_SendCommand(audio_sink_handle_, OMX_CommandStateSet, OMX_StateIdle, nullptr);
  if (err != OMX_ErrorNone) {
    LOGE << "set audio source component to state " << OMX_StateLoaded << "  failed: " << GetEnumName(err);
    return err;
  }
  // allocate buffers on audio source output port
  int index = 0;
  LOGI << "Buffer output size is: " << buffer_in_size;
  for (auto &out_buffer : in_buffer_sink_) {
    err = OMX_AllocateBuffer(audio_sink_handle_, &out_buffer, 0, nullptr, buffer_in_size);
    if (err != OMX_ErrorNone) {
      LOGE << "Allocate buffer " << index++ << " failed: " << GetEnumName(err);
    }
  }
  LOGI << "Before locking on idle state wait semaphore";
  tsem_down(sink_event_sem_.get());
  LOGI << "Sink semaphore " << std::showbase << std::hex << src_event_sem_.get() << " free";

  /** sending command to audio source component to go to executing state */
  err = OMX_SendCommand(audio_sink_handle_, OMX_CommandStateSet, OMX_StateExecuting, nullptr);
  if (err != OMX_ErrorNone) {
    LOGW << "set audio source component to state " << OMX_StateExecuting << "  failed: " << GetEnumName(err);
  }
  tsem_down(sink_event_sem_.get());

  //        FillBuffer(in_buffer_sink_[0]);
  //        FillBuffer(in_buffer_sink_[1]);
  //
  //        // ask the source component to fill the output buffer
  //        for (auto &in_buffer: in_buffer_sink_) {
  //            err = OMX_EmptyThisBuffer(audio_sink_handle_, in_buffer);
  //            if (err != OMX_ErrorNone) {
  //                LOGE << "Empty buffer failed: " << GetEnumName(err);
  //            }
  //        }

  return OMX_ErrorNone;
}
OMX_ERRORTYPE AppContext::Stop() {

  auto err = OMX_SendCommand(audio_sink_handle_, OMX_CommandStateSet, OMX_StateIdle, nullptr);
  if (OMX_ErrorNone != err) {
    LOGE << "set audio src component to state " << OMX_StateIdle << "  failed: " << GetEnumName(err);
  }
  LOGI << "All audio components transitioned to idle done.";
  // tsem_down

  // sending command to all components to go to loaded state
  err = OMX_SendCommand(audio_sink_handle_, OMX_CommandStateSet, OMX_StateLoaded, nullptr);
  if (OMX_ErrorNone != err) {
    LOGE << "set audio src component to state " << OMX_StateLoaded << "  failed: " << GetEnumName(err);
  }

  // free all the buffers.
  LOGI << "Free audio dec output ports";
  int index = 0;
  for (auto &input_buffer : in_buffer_sink_) {
    err = OMX_FreeBuffer(audio_sink_handle_, OMX_BASE_SOURCE_OUTPUTPORT_INDEX, input_buffer);
    if (OMX_ErrorNone != err) {
      LOGE << "Free buffer " << index++ << " failed: " << GetEnumName(err);
    }
  }
  tsem_down(sink_event_sem_.get());
  LOGI << "Source semaphore free";

  return err;
}

long AppContext::FillBuffer(OMX_U8 *buf, size_t size) {
  if (!input_pcm_file_stream_)
    return -1;
  if (!input_pcm_file_stream_->is_open())
    return -2;
  if (!input_pcm_file_stream_->good())
    return -3;

  if (input_pcm_file_stream_->read(reinterpret_cast<char *>(buf), static_cast<std::streamsize>(size))) {
    return input_pcm_file_stream_->gcount();
  }

  if (input_pcm_file_stream_->eof()) {
    SetEos(OMX_TRUE);
    LOGI << "File " << input_pcm_file_ << " end reached.";
    return input_pcm_file_stream_->gcount();
  }
  //        LOGI << "Fill buffer @" << static_cast<void *>(buf) << " size: " << size;
  return -3;
}

long AppContext::FillBuffer(OMX_BUFFERHEADERTYPE *buf) {
  auto ret = FillBuffer(buf->pBuffer, buf->nAllocLen);
  if (ret <= 0) {
    return ret;
  }
  buf->nFilledLen = ret;
  //  LOGI << "Fill buffer @" << static_cast<void *>(buf->pBuffer) << " size: " << buf->nAllocLen;
  if (ret < buf->nAllocLen) {
    buf->nFlags |= OMX_BUFFERFLAG_EOS;
  }
  //        buf->nTimeStamp =

  return 0;
}

long AppContext::SyncFillBuffer() {
  std::lock_guard<std::mutex> lock(buffer_mutex_);
  OMX_BUFFERHEADERTYPE *free_buffer = nullptr;

  auto tryCounter = 0;
  static auto wait_buffer_time = std::chrono::milliseconds(10);
  while (!IsEos() && nullptr == free_buffer) {
    for (auto &input_buffer : in_buffer_sink_) {
      if (input_buffer->nFilledLen == 0) {
        free_buffer = input_buffer;
        break;
      }
      // wait for the next round
      ++tryCounter;
      if (tryCounter % 10 == 0)
        LOGI << "Buffer is not ready after tried " << tryCounter << " times, waiting for next "
             << wait_buffer_time.count() << "ms";
      std::this_thread::sleep_for(std::chrono::milliseconds(wait_buffer_time));
    }
  }

  if (IsEos()) {
    return -1;
  }
  //        LOGI << "SyncFillBuffer: " << static_cast<void *>(free_buffer) << " nFilledLen: " <<
  //        free_buffer->nFilledLen;
  FillBuffer(free_buffer);

  auto err = OMX_EmptyThisBuffer(audio_sink_handle_, free_buffer);
  if (err != OMX_ErrorNone) {
    LOGE << "Empty buffer failed: " << GetEnumName(err);
  }
  return err;
}

} // namespace openmax::test
